#include "gtest/gtest.h"
#include "../BaseUtil/MyTest.h"
#include "DOMCoreImp.h"
#include "../DOMEventImp/DOMEventImp.h"
namespace DOMCoreImp {
using namespace DOMEvent;
using namespace DOMEventImp;

class NodeImpTest : public testing::Test {
protected:

	class DummyNodeImp : public NodeImp {
	public:
		DummyNodeImp(DOMString nodeName, DocumentImp * ownerDocument)
		: 	NodeImp(nodeName, ownerDocument) {}

		virtual unsigned short getNodeType() { return 99; }
	};

	virtual void SetUp() {
		doc = new DocumentImp();
		node = new DummyNodeImp("DUMMY", doc );

		child1 = new DummyNodeImp( "child1", doc );
		child2 = new DummyNodeImp( "child2", doc );

		alienDoc = new DocumentImp();
		alienNode = new DummyNodeImp( "AlienNode", alienDoc );

		evtClickForNode = createMouseClickEventFor( node );
		evtClickForChild1 = createMouseClickEventFor( child1 );

		evtListener = new DummyEventListener();
	}

	virtual void TearDown() {
		delete evtListener;
		delete evtClickForChild1;
		delete evtClickForNode;
		delete alienNode;
		delete alienDoc;
		delete child2;
		delete child1;
		delete node;
		delete doc;
	}

	static MouseEventImp * createMouseClickEventFor( NodeImp * target ) {
		return new MouseEventImp(
					target, NULL,
					MouseEventImp::EVENT_CLICK, true, true, NULL, 0,
					0, 0, 1, 1,
					false, false, false, false,
					MouseEvent::LEFT_MOUSE_BUTTON,
					NULL);
	}

	class DummyEventListener : public EventListener {
	public:
		DummyEventListener() : count_(0) {}

		int getCount() const { return count_; }

		virtual void handleEvent(Event * evt) {
			++count_;
		}
	private:
		int count_;
	};

	DocumentImp * doc;
	NodeImp * node;
	NodeImp * child1, * child2;
	DocumentImp * alienDoc;
	DummyNodeImp * alienNode;
	MouseEventImp * evtClickForNode, *evtClickForChild1;
	DummyEventListener * evtListener;
};

TEST_F(NodeImpTest, Inheritance) {
	NodePtr( new DummyNodeImp("DUMMY", NULL) );
}

TEST_F(NodeImpTest, Name) {
	ASSERT_STREQ( "DUMMY", node->getNodeName().c_str() );
}

TEST_F(NodeImpTest, Value) {
	ASSERT_STREQ( "", node->getNodeValue().c_str() );
	node->setNodeValue( "Anything" );
	ASSERT_STREQ( "Anything", node->getNodeValue().c_str() );
}

TEST_F(NodeImpTest, OwnerDocument) {
	ASSERT_EQ( doc, node->getOwnerDocument() );
	ASSERT_EQ( doc, node->getOwnerDocumentImp() );
}

TEST_F(NodeImpTest, ParentNode) {
	ASSERT_FALSE( node->getParentNode() );
	ElementImp* newElem = doc->createElementImp("newElem");
	ASSERT_TRUE( newElem );
	node->setParentNode( newElem );
	ASSERT_TRUE( node->getParentNode() );
}

TEST_F(NodeImpTest, ChildObservers) {
	ASSERT_FALSE( node->hasChildNodes() );
	ASSERT_FALSE( node->getFirstChild() );
	ASSERT_FALSE( node->getLastChild() );
	ASSERT_TRUE( node->getChildNodes() );

	node->appendChild( child1 );

	ASSERT_TRUE( node->hasChildNodes() );
	ASSERT_EQ( child1, node->getFirstChild() );
	ASSERT_EQ( child1, node->getLastChild() );
	ASSERT_EQ( 1ul, node->getChildNodes()->getLength() );
}

TEST_F(NodeImpTest, AppendChild) {
	ASSERT_EQ( child1, node->appendChild( child1 ) );
	ASSERT_EQ( child1, node->getChildNodes()->item( 0 ) );
	ASSERT_EQ( 1ul, node->getChildNodes()->getLength() );
	ASSERT_EQ( node, child1->getParentNode() );
}

TEST_F(NodeImpTest, InsertBefore) {
	ASSERT_EQ( child1, node->appendChild( child1 ) );
	ASSERT_EQ( child2, node->insertBefore( child2, child1 ) );
	ASSERT_EQ( child2, node->getChildNodes()->item( 0 ) );
	ASSERT_EQ( child1, node->getChildNodes()->item( 1 ) );
	ASSERT_EQ( node, child1->getParentNode() );
	ASSERT_EQ( node, child2->getParentNode() );
}

TEST_F(NodeImpTest, ReplaceChild) {
	ASSERT_EQ( child1, node->appendChild( child1 ) );
	ASSERT_EQ( child1, node->replaceChild( child2, child1 ) );
	ASSERT_EQ( child2, node->getChildNodes()->item( 0 ) );
	ASSERT_EQ( 1ul, node->getChildNodes()->getLength() );
	ASSERT_EQ( NULL, child1->getParentNode() );
	ASSERT_EQ( node, child2->getParentNode() );
}

TEST_F(NodeImpTest, RemoveChild) {
	ASSERT_EQ( child1, node->appendChild( child1 ) );
	ASSERT_EQ( child1, node->removeChild( child1 ) );
	ASSERT_EQ( 0ul, node->getChildNodes()->getLength() );
	ASSERT_EQ( NULL, child1->getParentNode() );
}

TEST_F(NodeImpTest, ChildThrowWRONG_DOCUMENT_ERR) {
	ASSERT_THROW( node->appendChild( alienNode ), DOMExceptionPtr );

	node->appendChild( child1 );
	ASSERT_THROW( node->insertBefore( alienNode, child1 ), DOMExceptionPtr );
	ASSERT_THROW( node->replaceChild( alienNode, child1 ), DOMExceptionPtr );
	ASSERT_EQ( NULL, alienNode->getParentNode() );
}

TEST_F(NodeImpTest, ChildThrowNOT_FOUND_ERR) {
	ASSERT_THROW( node->removeChild( NULL ), DOMExceptionPtr );
	ASSERT_THROW( node->removeChild( child1 ), DOMExceptionPtr );
	ASSERT_THROW( node->insertBefore( child2, child1 ), DOMExceptionPtr );
	ASSERT_THROW( node->replaceChild( alienNode, child1 ), DOMExceptionPtr );
	ASSERT_EQ( NULL, child1->getParentNode() );
	ASSERT_EQ( NULL, child2->getParentNode() );
	ASSERT_EQ( NULL, alienNode->getParentNode() );
}

TEST_F(NodeImpTest, AccessSiblings) {
	ASSERT_EQ( NULL, child1->getNextSibling() );
	ASSERT_EQ( NULL, child2->getPreviousSibling() );
	node->appendChild( child1 );
	node->appendChild( child2 );
	ASSERT_EQ( child2, child1->getNextSibling() );
	ASSERT_EQ( child1, child2->getPreviousSibling() );
	ASSERT_EQ( NULL, child1->getPreviousSibling() );
	ASSERT_EQ( NULL, child2->getNextSibling() );
}

TEST_F(NodeImpTest, AddEventListener) {
	node->addEventListener( MouseEventImp::EVENT_CLICK, evtListener, false );
	ASSERT_EQ( 0, evtListener->getCount() );
	evtClickForNode->setEventPhase( Event::AT_TARGET );
	node->dispatchEvent( evtClickForNode );
	ASSERT_EQ( 1, evtListener->getCount() );
}

TEST_F(NodeImpTest, RemoveEventListener) {
	node->addEventListener( MouseEventImp::EVENT_CLICK, evtListener, false );
	node->removeEventListener( MouseEventImp::EVENT_CLICK, evtListener, false );
	ASSERT_EQ( 0, evtListener->getCount() );
	evtClickForNode->setEventPhase( Event::AT_TARGET );
	node->dispatchEvent( evtClickForNode );
	ASSERT_EQ( 0, evtListener->getCount() );
}

TEST_F(NodeImpTest, AddEventListenerForCapture) {
	node->appendChild( child1 );
	node->addEventListener( MouseEventImp::EVENT_CLICK, evtListener, true );

	ASSERT_EQ( 0, evtListener->getCount() );
	evtClickForChild1->setEventPhase( Event::CAPTURING_PHASE );
	node->dispatchEvent( evtClickForChild1 );
	ASSERT_EQ( 1, evtListener->getCount() );

	evtClickForChild1->setEventPhase( Event::AT_TARGET );
	child1->dispatchEvent( evtClickForChild1 );
	ASSERT_EQ( 1, evtListener->getCount() );

	evtClickForChild1->setEventPhase( Event::BUBBLING_PHASE );
	node->dispatchEvent( evtClickForChild1 );
	ASSERT_EQ( 2, evtListener->getCount() );
}

TEST_F(NodeImpTest, RemoveEventListenerForCapture) {
	node->appendChild( child1 );
	node->addEventListener( MouseEventImp::EVENT_CLICK, evtListener, true );
	node->removeEventListener( MouseEventImp::EVENT_CLICK, evtListener, true );

	ASSERT_EQ( 0, evtListener->getCount() );
	evtClickForChild1->setEventPhase( Event::CAPTURING_PHASE );
	node->dispatchEvent( evtClickForChild1 );
	ASSERT_EQ( 0, evtListener->getCount() );
}

TEST_F(NodeImpTest, DifferentiatingEventsByEventType) {
	node->addEventListener( MouseEventImp::EVENT_MOUSE_DOWN, evtListener, false );

	ASSERT_EQ( 0, evtListener->getCount() );
	evtClickForChild1->setEventPhase( Event::AT_TARGET );
	node->dispatchEvent( evtClickForNode );
	ASSERT_EQ( 0, evtListener->getCount() );
}

TEST_F(NodeImpTest, CreateClickEvent) {
	MouseEvent * newEvt = dynamic_cast< MouseEvent * >( node->createEvent( MouseEventImp::EVENT_CLICK ) );
	ASSERT_TRUE( newEvt );

	ASSERT_EQ( node, newEvt->getTarget() );
	ASSERT_STREQ( MouseEventImp::EVENT_CLICK.c_str(), newEvt->getType().c_str() );
	ASSERT_EQ( true, newEvt->isBubbles() );
	ASSERT_EQ( true, newEvt->isCancelable() );
	delete newEvt;
}

TEST_F(NodeImpTest, CreateOtherMouseEvents) {
	// this information is from http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-MouseEvent
	const DOMString mouseEventTypes[] = { MouseEventImp::EVENT_CLICK,
									MouseEventImp::EVENT_MOUSE_DOWN,
									MouseEventImp::EVENT_MOUSE_UP,
									MouseEventImp::EVENT_MOUSE_OVER,
									MouseEventImp::EVENT_MOUSE_MOVE,
									MouseEventImp::EVENT_MOUSE_OUT };
	const bool bubbles[]     = { true, true, true, true, true,  true };
	const bool cancelables[] = { true, true, true, true, false, true };

	for( unsigned int idx = 0; idx < sizeof(bubbles)/sizeof(bool); ++idx ) {
		const DOMString mouseEventType = mouseEventTypes[idx];
		const bool bubble = bubbles[idx];
		const bool cancelable = cancelables[idx];

		MouseEvent * newEvt = dynamic_cast< MouseEvent * >( node->createEvent( mouseEventType ) );
		ASSERT_TRUE( newEvt );

		ASSERT_EQ( node, newEvt->getTarget() );
		ASSERT_STREQ( mouseEventType.c_str(), newEvt->getType().c_str() );
		ASSERT_EQ( bubble, newEvt->isBubbles() );
		ASSERT_EQ( cancelable, newEvt->isCancelable() );

		delete newEvt;
	}
}

}
